﻿Imports HtmlAgilityPack
Imports System.Windows.Threading

Class MainWindow



    ''' <summary>
    ''' Url du character bazaar
    ''' </summary>
    ''' <remarks></remarks>
    Friend Shared BaseBazaarUrl As String = "https://forums.eveonline.com/default.aspx?g=topics&f=277"

    ''' <summary>
    ''' Url de EveBoard
    ''' </summary>
    ''' <remarks></remarks>
    Friend Shared BaseEveboardUrl As String = "http://eveboard.com/pilot/"

    ''' <summary>
    ''' Requete XPath pour les  posts sur le forum
    ''' </summary>
    ''' <remarks></remarks>
    Private Shared XpathExpr1 = Xml.XPath.XPathExpression.Compile("//tr[@class='topicRow post']")

    ''' <summary>
    ''' Requete XPath pour les  posts sur le forum ( v2)
    ''' </summary>
    ''' <remarks></remarks>
    Private Shared XpathExpr2 = Xml.XPath.XPathExpression.Compile("//tr[@class='topicRow_Alt post_alt']")

    'Requete XPath pour pour les  posts : colonne description
    Private Shared XpathExprDesc = Xml.XPath.XPathExpression.Compile("td[1]/div/a")

    'Requete XPath pour pour les  posts : colonne auteur
    Private Shared XpathExprAuthor = Xml.XPath.XPathExpression.Compile("td[2]/div/a")

    'Requete XPath pour pour les  posts : colonne nombre de  reponses
    Private Shared XpathExprReplies = Xml.XPath.XPathExpression.Compile("td[3]")

    'Requete XPath pour pour les  posts : colonne nombre de  vues
    Private Shared XpathExprViews = Xml.XPath.XPathExpression.Compile("td[4]")

    'Requete XPath pour pour les  posts : colonne nombre  de likes
    Private Shared XpathExprLikes = Xml.XPath.XPathExpression.Compile("td[5]")

    'Requete XPath pour pour les  posts : colonne date heure de  la derniere  reponse  au post
    Private Shared XpathExprLastPost = Xml.XPath.XPathExpression.Compile("td[6]")

    'Requete XPath pour EVEBOARD : tentative pour determiner si la  page est  valide
    Private Shared EveBoardExistXpathExpr = Xml.XPath.XPathExpression.Compile( _
        "//span[@class='greyText']")

    'Requete XPath pour EVEBOARD : nombre de  sp du pilote
    Friend Shared EveBoardTotalSPXpathExpr = Xml.XPath.XPathExpression.Compile( _
        "html/body/center/table/tr/td/table/tr[3]/td[2]/table[3]/tr/td[2]/table/tr[6]/td[2]")

    'Requete XPath pour EVEBOARD : date de naissance  du pilote
    Friend Shared EveBoardDOBXpathExpr = Xml.XPath.XPathExpression.Compile( _
      "html/body/center/table/tr/td/table/tr[3]/td[2]/table[3]/tr/td[2]/table/tr[4]/td[2]")

    'Requete XPath pour EVEBOARD : skills  du pilote ( table les contenant)
    Friend Shared EveBoardSkillsXpathExpr = Xml.XPath.XPathExpression.Compile( _
      "html/body/center/table/tr/td/table/tr[3]/td[2]/table[5]/tr/td/table/tr/td/table")

    'Requete XPath pour EVEBOARD : skills  du pilote ( table les contenant)
    Friend Shared EveBoardSkillInfoXpathExpr = Xml.XPath.XPathExpression.Compile( _
      "tr/td[2]")

    ''' <summary>
    ''' Traite  la  pagedu character  bazaar  et  charge les  pilotes 
    ''' </summary>
    ''' <param name="pageid">numero de la  page</param>
    ''' <param name="container">tableau devant contenir le resultat. Utilise uniquement  pour charger  des  pilotes  dans une  liste existante deja</param>
    ''' <param name="loadskills">Charger  les  skills sur  EVEBOARD</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function ProcessPage(ByVal pageid As Integer, Optional ByVal container As List(Of BazaarRow) = Nothing, Optional ByVal loadskills As Boolean = False) As List(Of BazaarRow)

        Dim loader = New HtmlWeb()

        Dim doc = loader.Load(BaseBazaarUrl & "&p=" & pageid)

        Dim xpath = doc.CreateNavigator()

        Dim rows1 As Xml.XPath.XPathNodeIterator = xpath.Select(XpathExpr1)
        Dim rows2 As Xml.XPath.XPathNodeIterator = xpath.Select(XpathExpr2)

        Dim localcontainer As New List(Of BazaarRow)

        If container IsNot Nothing Then
            localcontainer = container
        End If

        Do While rows1.MoveNext()
            localcontainer.Add(ProcessRow(rows1.Current, loadskills))
        Loop

        Do While rows2.MoveNext()
            localcontainer.Add(ProcessRow(rows2.Current, loadskills))
        Loop

        Return localcontainer
    End Function

    ''' <summary>
    ''' Transform un resultat  Xpath  ( html) en  Objet BazaarRow
    ''' </summary>
    ''' <param name="r"></param>
    ''' <param name="loadskills"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function ProcessRow(ByVal r As Xml.XPath.XPathNavigator, Optional ByVal loadskills As Boolean = False) As BazaarRow

        Dim result As New BazaarRow

        Dim descnav As Xml.XPath.XPathNavigator = r.SelectSingleNode(XpathExprDesc)

        result.Description = descnav.Value

        Dim posturl = descnav.GetAttribute("href", "")

        result.Url = New Uri("https://forums.eveonline.com" & posturl)

        result.RowID = CLng(posturl.Split(New String() {"&amp;"}, StringSplitOptions.RemoveEmptyEntries)(1).Trim().Replace("t=", "").Trim())

        Dim authornav As Xml.XPath.XPathNavigator = r.SelectSingleNode(XpathExprAuthor)

        result.Author = authornav.Value

        result.AuthorGateUrl = New Uri(authornav.GetAttribute("href", ""))

        result.AuthorEveBoardUrl = New Uri(BaseEveboardUrl & result.Author.Replace(" ", "_"))

        result.RepliesCount = CType(r.SelectSingleNode(XpathExprReplies), Xml.XPath.XPathNavigator).ValueAsInt

        result.ViewCount = CInt(CType(r.SelectSingleNode(XpathExprViews), Xml.XPath.XPathNavigator).Value.Replace(",", ""))

        result.LikeCount = CType(r.SelectSingleNode(XpathExprLikes), Xml.XPath.XPathNavigator).ValueAsInt

        Dim lastposnav As Xml.XPath.XPathNavigator = r.SelectSingleNode(XpathExprLastPost)

        Dim lastpostdate = lastposnav.Value.Trim().Substring(0, 16)

        result.LastPostDate = DateTime.Parse(lastpostdate, Globalization.DateTimeFormatInfo.InvariantInfo)

        If loadskills Then
            Try
                result.Execute(Nothing)
            Catch ex As Exception

            End Try
        End If

        Return result
    End Function

    Friend Shared xmlskillsdata As Dictionary(Of String, String)
    Private Sub MainWindow_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim xfile = XElement.Load("SkillData.xml")

        Dim x = (From elt In xfile.<SkillInfo> Order By elt.@Type Select elt.@Type Distinct).ToList()

        x.Insert(0, "(Aucun Filtre)")

        cbxSkill.ItemsSource = x

        xmlskillsdata = (From elt In xfile.<SkillInfo> Order By elt.@Type Select elt.@Type, elt.@Group Distinct).ToDictionary(Function(et) et.Type, Function(et) et.Group)

        SkillFilterDGrid.ItemsSource = SkillFilters
    End Sub

    Dim timer As DispatcherTimer
    Private Sub MonitoringBt_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MonitoringBt.Click

        Dim collection = New ObjectModel.ObservableCollection(Of BazaarRow)(ProcessPage(1, loadskills:=True))

        Dim cvs As CollectionViewSource = Me.Resources("BazaarRowSource")

        cvs.Source = collection

        If timer Is Nothing Then
            timer = New DispatcherTimer(DispatcherPriority.Background, Dispatcher)
            timer.Interval = TimeSpan.FromMinutes(5)

            Dim f = Sub(s As Object, a As EventArgs)

                        Dim coll As ObjectModel.ObservableCollection(Of BazaarRow) = timer.Tag

                        Dim newcoll = ProcessPage(1, loadskills:=True)

                        Dim lastrow = newcoll.Except(newcoll.Intersect(coll, BazaarRow.BazaarRowComparer), BazaarRow.BazaarRowComparer)

                        For Each elt In lastrow
                            elt.Description = "[" & Now.ToLongTimeString() & "] " & elt.Description
                            coll.Insert(0, elt)
                        Next
                    End Sub
            AddHandler timer.Tick, f
        End If

        timer.Stop()

        timer.Tag = collection

        timer.Start()

    End Sub

    Private Sub OnHyperlinkClick(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        Dim link As Hyperlink = e.OriginalSource

        Dim browser = New Process()

        Dim startinfo = New ProcessStartInfo()
        startinfo.UseShellExecute = True
        startinfo.FileName = link.NavigateUri.ToString().Replace("amp;", "")
        browser.StartInfo = startinfo
        browser.Start()
    End Sub

    Private DoingJob As Boolean = False

    Private Sub ResetDBBt_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles ResetDBBt.Click

        If DoingJob Then Return

        'Dim resultlist As New List(Of BazaarRow)

        Dim worker As New ComponentModel.BackgroundWorker()
        worker.WorkerReportsProgress = True

        AddHandler worker.DoWork, AddressOf workerDoWork
        AddHandler worker.ProgressChanged, AddressOf workerProgressChanged
        AddHandler worker.RunWorkerCompleted, AddressOf workerRunWorkerCompleted

        DoingJob = True

        worker.RunWorkerAsync()

    End Sub

    Private Sub workerDoWork(ByVal sender As Object, ByVal e As ComponentModel.DoWorkEventArgs)
        Dim resultlist As New List(Of BazaarRow)

        For i = 1 To 30
            Try
                ProcessPage(i, resultlist, True)
                CType(sender, ComponentModel.BackgroundWorker).ReportProgress(Math.Min(100, CInt(100 * i / 30)))
            Catch ex As Exception

            End Try
        Next

        e.Result = resultlist
    End Sub

    Private Sub workerProgressChanged(ByVal sender As Object, ByVal e As ComponentModel.ProgressChangedEventArgs)
        PBar.Value = e.ProgressPercentage
    End Sub

    Private Sub workerRunWorkerCompleted(ByVal sender As Object, ByVal e As ComponentModel.RunWorkerCompletedEventArgs)
        DoingJob = False
        Dim cvs As CollectionViewSource = Me.Resources("BazaarRowSource")

        cvs.Source = e.Result
        'DGrid.ItemsSource = e.Result
    End Sub

    Friend Shared Function EveBoardExist(ByVal r As Xml.XPath.XPathNavigator) As Boolean
        If r.Select(EveBoardExistXpathExpr) IsNot Nothing Then
            MessageBox.Show(r.SelectSingleNode(EveBoardExistXpathExpr).Value)
        End If
        Return r.SelectSingleNode(EveBoardExistXpathExpr) IsNot Nothing
    End Function

    Private Function EveBoardNeedPassWord(ByVal r As Xml.XPath.XPathNavigator) As Boolean

    End Function

    Private Function EveBoardIsSomethingWrong(ByVal r As Xml.XPath.XPathNavigator) As Boolean
        Return False
    End Function

    Private Function EveBoardGetDOB(ByVal r As Xml.XPath.XPathNavigator) As DateTime

    End Function

    Private Function EveBoardGetTotalSP(ByVal r As Xml.XPath.XPathNavigator) As DateTime

    End Function

    'colorer les lignes en fonction de 



    Private Sub SearchBySkillBt_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles SearchBySkillBt.Click
        If sender IsNot e.Source Then Return
        Dim cvs As CollectionViewSource = Me.Resources("BazaarRowSource")
        Dim sf = New SkillFilter(SkillFilters) With {.Skill = cbxSkill.SelectedItem.ToString(), .Level = CInt(txtSkill.Text.Trim())}


        If SkillFilters.Contains(sf) Then

            Dim ooo = From elt In SkillFilters Where elt.Skill = sf.Skill And elt.Level <> sf.Level Select elt

            Dim mmm = ooo.FirstOrDefault()

            If mmm IsNot Nothing Then

                SkillFilters.Remove(mmm)

                SkillFilters.Add(sf)
                If cvs.View IsNot Nothing Then
                    cvs.View.Refresh()
                End If

            End If

        Else

            Dim ooo = From elt In SkillFilters Where elt.Skill.ToLower().Trim() = sf.Skill.ToLower().Trim() Select elt

            Dim mmm = ooo.FirstOrDefault()

            If mmm IsNot Nothing Then
                SkillFilters.Remove(mmm)
            End If


            SkillFilters.Add(sf)

            If cvs.View IsNot Nothing Then
                cvs.View.Refresh()
            End If
        End If



    End Sub

    Private SkillFilters As New ObjectModel.ObservableCollection(Of SkillFilter)

    Private Sub BazaarRowSource_Filter(ByVal sender As System.Object, ByVal e As System.Windows.Data.FilterEventArgs)
        Dim it As BazaarRow = e.Item

        e.Accepted = False

        If it.Skills Is Nothing And cbxSkill.SelectedIndex = 0 Then
            e.Accepted = True
            Return

        End If
        If it.Skills Is Nothing Then
            e.Accepted = False
            Return

        End If

        'Dim o = it.Skills.Where(Function(r) r.Name.ToLower().Trim() = cbxSkill.SelectedItem.ToString().ToLower().Trim()).FirstOrDefault()

        Dim o = From s In it.Skills Join sf In SkillFilters On s.Name Equals sf.Skill
                Where s.Level >= sf.Level
                Select s


        If o Is Nothing Then
            e.Accepted = False
        Else

            'Dim l = -1

            'Try
            '    l = CInt(txtSkill.Text.Trim())
            'Catch ex As Exception

            'End Try

            'e.Accepted = (o.Level >= l)
            e.Accepted = (o.Count() = SkillFilters.Count)

        End If

    End Sub
End Class

Public Class DateConverter
    Implements IValueConverter


    Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
        If value Is Nothing Then Return ""

        Return CType(value, DateTime).ToLongDateString()
    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack

    End Function
End Class

Public Class BazaarRow
    Implements ICommand
    Implements ComponentModel.INotifyPropertyChanged

    Dim _SkillCost As Long?



    Public Property Description As String

    Public Property Url As Uri

    Public Property RowID As Long

    Public Property Author As String

    Public Property AuthorGateUrl As Uri

    Public Property AuthorEveBoardUrl As Uri

    Public Property RepliesCount As Integer

    Public Property ViewCount As Integer

    Public Property LikeCount As Integer

    Public Property LastPostDate As DateTime

    Public Property LastPostAuhor As String

    Public Property TotalSP As Long?

    Public Property DOB As DateTime?

    Public Property Skills As ObjectModel.ObservableCollection(Of EveBoardSkillRow)

    Private Function CanExecute(ByVal parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
        Return True
    End Function
    Private Event CanExecuteChanged(ByVal sender As Object, ByVal e As System.EventArgs) Implements System.Windows.Input.ICommand.CanExecuteChanged
    Friend Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
        Dim loader = New HtmlWeb()

        Dim doc = loader.Load(Me.AuthorEveBoardUrl.ToString())

        Dim xpath = doc.CreateNavigator()

        Dim tmpdob = xpath.SelectSingleNode(MainWindow.EveBoardDOBXpathExpr)

        Dim tmptotal = xpath.SelectSingleNode(MainWindow.EveBoardTotalSPXpathExpr)

        If tmpdob IsNot Nothing Then
            Me.DOB = DateTime.Parse(tmpdob.Value.ToString(), Globalization.DateTimeFormatInfo.InvariantInfo)
        End If


        If tmptotal IsNot Nothing Then
            Me.TotalSP = CLng(tmptotal.Value.Replace(",", ""))
        End If

        OnPropertyChanged("DOB")
        OnPropertyChanged("TotalSP")

        Dim ttt = xpath.SelectSingleNode(MainWindow.EveBoardSkillsXpathExpr)

        If ttt Is Nothing Then Return

        Dim tmpskills As Xml.XPath.XPathNodeIterator = ttt.Select(MainWindow.EveBoardSkillInfoXpathExpr)

        Dim tmpskilllist = New List(Of EveBoardSkillRow)

        Do While tmpskills.MoveNext()

            Dim skilldata = tmpskills.Current.Value.Split(New String() {"/"}, StringSplitOptions.RemoveEmptyEntries)

            If skilldata.Length = 4 Then
                tmpskilllist.Add( _
                    New EveBoardSkillRow() With {
                        .Name = skilldata(0).Trim(),
                        .Rank = CInt(skilldata(1).Replace("Rank", "").Trim()),
                        .Level = CInt(skilldata(2).Replace("Level:", "").Trim()),
                        .Group = MainWindow.xmlskillsdata(skilldata(0).Trim()),
                .SkillPoint = CInt(skilldata(3).Split(New String() {"of"}, StringSplitOptions.RemoveEmptyEntries)(0).Trim().Replace(",", "").Replace("SP:", "").Trim())
                    })
            End If

        Loop

        Me.Skills = New ObjectModel.ObservableCollection(Of EveBoardSkillRow)(tmpskilllist)


        OnPropertyChanged("Skills")

        Dim o = 1
        'MessageBox.Show(MainWindow.EveBoardExist(xpath))
    End Sub

    Private Sub OnPropertyChanged(ByVal propname As String)
        RaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs(propname))
    End Sub

    Private Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

    Private _SkillStat As IEnumerable
    Public ReadOnly Property SkillStat As IEnumerable
        Get
            If Skills IsNot Nothing Then
                If Skills.Count > 0 Then
                    If _SkillStat Is Nothing Then
                        Dim q = From elt In Skills
                                     Group By elt.Level Into Count()

                        '_SkillStat = From elt In q Order By elt.Level Select Level = "Level" & elt.Level, Count = 100 * (elt.Count / Skills.Count)
                        _SkillStat = From elt In q Order By elt.Level Select Level = "Level" & elt.Level, Count = 100 * (elt.Count / q.Max(Function(a) a.Count))
                        'OnPropertyChanged("SkillStat")
                    End If
                End If
            End If
            Return _SkillStat
        End Get
    End Property

    Public ReadOnly Property SkillCost As Long?
        Get
            If TotalSP IsNot Nothing Then
                If TotalSP.HasValue Then
                    If _SkillCost Is Nothing Then
                        _SkillCost = 600000000 * (TotalSP / (2700 * 30 * 24))
                    End If
                End If
            End If
            Return _SkillCost
        End Get
    End Property

    Private Class Comparer
        Implements IEqualityComparer(Of BazaarRow)

        Private Function ComparerEquals(ByVal x As BazaarRow, ByVal y As BazaarRow) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of BazaarRow).Equals
            Return x.RowID = y.RowID
        End Function

        Private Function ComparerGetHashCode(ByVal obj As BazaarRow) As Integer Implements System.Collections.Generic.IEqualityComparer(Of BazaarRow).GetHashCode
            Return obj.RowID.GetHashCode()
        End Function
    End Class

    Public Shared BazaarRowComparer As IEqualityComparer(Of BazaarRow) = New Comparer()
End Class

Public Class EveBoardSkillRow

    Public Property Name As String

    Public Property Rank As Integer

    Public Property Group As String

    Public Property Level As Integer

    Public Property SkillPoint As Integer
End Class

Public Class SkillFilter
    Implements ICommand

    Public Property Skill As String

    Public Property Level As Integer

    Private container As ObjectModel.ObservableCollection(Of SkillFilter)

    Sub New()

    End Sub

    Sub New(ByVal container As Object)
        Me.container = container
    End Sub

    'Public Overrides Function GetHashCode() As Integer
    '    Return Skill.ToLower().Trim().GetHashCode()
    'End Function

    'Public Overrides Function Equals(ByVal obj As Object) As Boolean
    '    Return Skill.ToLower().Trim().Equals(obj.ToString().ToLower().Trim())
    'End Function

    Public Overrides Function ToString() As String
        Return Skill & " >= " & Level
    End Function

    Public Function CanExecute(ByVal parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
        Return True
    End Function

    Public Event CanExecuteChanged(ByVal sender As Object, ByVal e As System.EventArgs) Implements System.Windows.Input.ICommand.CanExecuteChanged

    Public Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
        container.Remove(Me)

        Dim ooo = (From elt In container Where elt.Skill.ToLower().Trim() = Skill.ToLower().Trim() Select elt).ToList()

        ooo.ForEach(Sub(oo) container.Remove(oo))
    End Sub

    Public Function Valid(ByVal EveBoardSkillRow As EveBoardSkillRow) As Boolean
        If EveBoardSkillRow.Name <> Skill Then
            Return False
        End If

        Return EveBoardSkillRow.Level >= Level
    End Function
End Class